unit tileselectionunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, constants, fpjson, tilesetgraphicsunit, palettehelper;


const
  TILE_SELECTION_ANY_TILE = -1;

type
  ATileSelectionCallback = procedure (SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean) of object;

  { TTileSelection }

  TTileSelection = class
  private
    IsSingleLayer: Boolean;
    FData: array of array of Integer;
    procedure SetData(Layer, X, Y: Integer; Value: Integer); inline;
    function GetData(Layer, X, Y: Integer): Integer; inline;
  public
    Width, Height: Integer;
    property Data[Layer, X, Y: Integer]: Integer read GetData write SetData;

    procedure Init(AWidth, AHeight: Integer; AIsSingleLayer: Boolean);
    function GetATileSelectFn(PaletteY: Integer; TilesetGraphics: TTilesetGraphics; var StartY: Integer): ATileSelectionCallback;
    procedure SelectA1Tile(SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean);
    procedure SelectA2Tile(SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean);
    procedure SelectA3Tile(SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean);
    procedure SelectA4Tile(SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean);
    procedure SelectA5Tile(SelX, SelY: Integer; StartY: Integer; PaletteIndex: Integer; IsWorldType: Boolean);
    procedure EraseUpperLayer(SelX, SelY: Integer; InlcudingZ1: Boolean);
    procedure SelectUpperTile(SelX, SelY: Integer; Layer: Integer; PaletteIndex: Integer);
  end;

  SelectionUpdateCallback = procedure(Selection: TTileSelection) of object;

implementation

procedure TTileSelection.SetData(Layer, X, Y: Integer; Value: Integer); inline;
begin
  if (IsSingleLayer and (Layer = 0)) or (not IsSingleLayer and (Layer >= 0)
                     and (Layer <= 4)) then begin
    FData[Layer][Y * Width + X] := Value;
  end;
end;

function TTileSelection.GetData(Layer, X, Y: Integer): Integer;
begin
  if (IsSingleLayer and (Layer = 0)) or (not IsSingleLayer and (Layer >= 0)
                     and (Layer <= 4)) then begin
    GetData := FData[Layer][Y * Width + X];
  end;
end;

procedure TTileSelection.Init(AWidth, AHeight: Integer; AIsSingleLayer: Boolean);
var
  NumLayers: Integer = 1;
begin
  IsSingleLayer := AIsSingleLayer;
  Width := AWidth;
  Height := AHeight;
  if not IsSingleLayer then
    NumLayers := 5;

  {TODO: find off-by-one error somewhere that forces me to add +1 (but for now,
  if I make it smaller, I get sigsegv, so adding +1 is a hacky fix)}
  SetLength(FData, NumLayers, (Width +1) * (Height + 1));
end;

function TTileSelection.GetATileSelectFn(PaletteY: Integer;
  TilesetGraphics: TTilesetGraphics; var StartY: Integer
  ): ATileSelectionCallback;
var
  CurrentHeightOffset: Integer = 0;

  function CheckSet(SetNumber: Integer; Height: Integer): Boolean;
  begin
    CheckSet := False;
    if TilesetGraphics.HasSet(SetNumber) then begin
      if PaletteY - CurrentHeightOffset < Height then
        CheckSet := True
      else
        Inc(CurrentHeightOffset, Height);
    end;
  end;

begin
  if CheckSet(0, 2) then
    GetATileSelectFn := @SelectA1Tile
  else if CheckSet(1, 4) then
    GetATileSelectFn := @SelectA2Tile
  else if CheckSet(2, 4) then
    GetATileSelectFn := @SelectA3Tile
  else if CheckSet(3, 6) then
    GetATileSelectFn := @SelectA4Tile
  else
    GetATileSelectFn := @SelectA5Tile;

  StartY := CurrentHeightOffset;
end;

procedure TTileSelection.SelectA1Tile(SelX, SelY: Integer; StartY: Integer;
  PaletteIndex: Integer; IsWorldType: Boolean);
var
  TileId: Integer;
begin
  TileId := SubBlockIds[PaletteIndex - StartY*8].SelectedAT;
  if (PaletteIndex >= 1) and (PaletteIndex <= 3) and not IsSingleLayer then begin
    Data[0, SelX, SelY] := SubBlockIds[0].SelectedAT;
    Data[1, SelX, SelY] := TileId;
  end else begin
    Data[0, SelX, SelY] := TileId;
    if not IsSingleLayer then
      Data[1, SelX, SelY] := TILE_ID_ERASER;
  end;

  EraseUpperLayer(SelX, SelY, False);
end;

procedure TTileSelection.SelectA2Tile(SelX, SelY: Integer; StartY: Integer;
  PaletteIndex: Integer; IsWorldType: Boolean);
var
  TileId: Integer;
  LayerId: Integer = 0;
begin
  TileId := TILE_ID_A2 + PaletteIndex*48 - StartY*8*48;
  LayerId := 0;
  if not IsSingleLayer and ((PaletteIndex mod 8) >= 4) then begin
    Data[0, SelX, SelY] := 0;
    Data[1, SelX, SelY] := TileId;
  end
  else if not IsSingleLayer and ((PaletteIndex mod 8) < 4)
                       and ((PaletteIndex mod 2) = 1) then begin

    if IsWorldType then begin
      Data[0, SelX, SelY] := TileId - 48;
      Data[1, SelX, SelY] := TileId;
    end
    else begin
      Data[0, SelX, SelY] := TileId;
      Data[1, SelX, SelY] := TILE_ID_ERASER;
    end;

  end
  else if not IsSingleLayer then begin
    Data[0, SelX, SelY] := TileId;
    Data[1, SelX, SelY] := TILE_ID_ERASER;
  end
  else begin
    Data[0, SelX, SelY] := TileId;
  end;

  EraseUpperLayer(SelX, SelY, False);
end;

procedure TTileSelection.SelectA3Tile(SelX, SelY: Integer; StartY: Integer;
  PaletteIndex: Integer; IsWorldType: Boolean);
var
  TileId: Integer;
begin
  TileId := TILE_ID_A3 + PaletteIndex * 48 - StartY*8*48;
  Data[0, SelX, SelY] := TileId;
  EraseUpperLayer(SelX, SelY, True);
end;

procedure TTileSelection.SelectA4Tile(SelX, SelY: Integer; StartY: Integer;
  PaletteIndex: Integer; IsWorldType: Boolean);
var
  TileId: Integer;
begin
  TileId := TILE_ID_A4 + PaletteIndex * 48 - StartY*8*48;
  Data[0, SelX, SelY] := TileId;
  if not IsSingleLayer then
      Data[1, SelX, SelY] := TILE_ID_ERASER;
  EraseUpperLayer(SelX, SelY, True);
end;

procedure TTileSelection.SelectA5Tile(SelX, SelY: Integer; StartY: Integer;
  PaletteIndex: Integer; IsWorldType: Boolean);
begin
  Data[0, SelX, SelY] := TILE_ID_A5 + PaletteIndex - StartY*8;
  EraseUpperLayer(SelX, SelY, True);
end;

procedure TTileSelection.EraseUpperLayer(SelX, SelY: Integer; InlcudingZ1: Boolean);
begin
  if not IsSingleLayer then begin
    if InlcudingZ1 then
      Data[1, SelX, SelY] := TILE_ID_ERASER;
    Data[2, SelX, SelY] := TILE_ID_ERASER;
    Data[3, SelX, SelY] := TILE_ID_ERASER;
  end;
end;

procedure TTileSelection.SelectUpperTile(SelX, SelY: Integer; Layer: Integer; PaletteIndex: Integer);
var
  TileId, LayerOffset: Integer;
begin
  LayerOffset := (Layer - TILE_TYPE_B) * 256;
  TileId := LayerOffset + PaletteIndex;
  if (TileId = 0) and not IsSingleLayer then begin
    Data[2, SelX, SelY] := TILE_ID_ERASER;
    Data[3, SelX, SelY] := TILE_ID_ERASER;
  end else if IsSingleLayer then
    Data[0, SelX, SelY] := TileId
  else
    Data[3, SelX, SelY] := TileId;
end;

end.

